/*------------------------------------------------------------------------------
Copyright (c) 2009 BlueChip, Cyborg Systems

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
------------------------------------------------------------------------------*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "peekpoke.h"

#ifndef PCDEMO
# include <gccore.h>
# include <grrlib.h>
#endif

#include "bmf.h"

/*
BMFGenerator  http://www.angelcode.com/products/bmfont/

Binary file layout

This section describes the layout of the tags in the binary file format. To
understand what each tag means refer to the file tags section.


The first three bytes are the file identifier and must always be 66, 77, 70, or
"BMF". The fourth byte gives the format version, currently it must be 3.

# Version 1 (introduced with application version 1.8).
# Version 2 (introduced with application version 1.9) added the outline field in
            the infoBlock and the encoded field in the commonBlock.
# Version 3 (introduced with application version 1.10) removed the encoded field
            in the commonBlock, and added the alphaChnl, redChnl, greenChnl,
            blueChnl instead. The size of each block is now stored without
            accounting for the size field itself. The character id in the
            charsBlock and the kerningPairsBlock was increased to 4 bytes to
            support the full unicode range.


Following the first four bytes is a series of blocks with information. Each
block starts with a one byte block type identifier, followed by a 4 byte integer
that gives the size of the block, not including the block type identifier and
the size value.


Block type 1: info
--------------------------------------------------------------------------------
Field        Size  Type  Off  Comment
--------------------------------------------------------------------------------
fontSize      2    int    0   Height in pixels
bitField      1    bits   2   0:smooth, 1:unicode, 2:italic, 3:bold,
                              4:fixedHeigth, 5-7:reserved
charSet       1    uint   3
stretchH      2    uint   4   Height percentage
aa            1    uint   6
paddingUp     1    uint   7   Padding above
paddingRight  1    uint   8   Padding right
paddingDown   1    uint   9   Padding below
paddingLeft   1    uint  10   Padding left
spacingHoriz  1    uint  11   Spacing horizontal
spacingVert   1    uint  12   Spacing vertical
outline       1    uint  13   Outline Thickness (new in v2)
fontName      n+1  str   14   Font Name (NULL terminated string of length n)

This structure gives the layout of the fields. Remember that there should be no
padding between members. Allocate the size of the block using the blockSize, as
following the block comes the font name, including the terminating null char.
Most of the time this block can simply be ignored.


Block type 2: common
--------------------------------------------------------------------------------
Field        Size  Type  Off  Comment
--------------------------------------------------------------------------------
lineHeight      2  uint   0
base            2  uint   2
scaleW          2  uint   4   image width
scaleH          2  uint   6   image height
pages           2  uint   8
bitField        1  bits  10   bits 0-6: reserved, bit 7: packed
alphaChnl       1  uint  11
redChnl         1  uint  12
greenChnl       1  uint  13
blueChnl        1  uint  14


Block type 3: pages
--------------------------------------------------------------------------------
Field        Size     Type     Off  Comment
--------------------------------------------------------------------------------
pageNames    p*(n+1)  strings  0    p null terminated strings, each of length n

This block gives the name of each texture file with the image data for the
characters. The string pageNames holds the names separated and terminated by
null chars. Each filename has the same length, so once you know the size of the
first name, you can easily determine the position of each of the names. The id
of each page is the zero-based index of the string name.


Block type 4: chars
--------------------------------------------------------------------------------
Field        Size  Type  Offset     Comment
--------------------------------------------------------------------------------
id              4  uint  (c*20)+ 0  These fields are repeated until all
                                    characters have been described
x               2  uint  (c*20)+ 4
y               2  uint  (c*20)+ 6
width           2  uint  (c*20)+ 8
height          2  uint  (c*20)+10
xoffset         2  int   (c*20)+12
yoffset         2  int   (c*20)+14
xadvance        2  int   (c*20)+16
page            1  uint  (c*20)+18
chnl            1  uint  (c*20)+19

The number of characters in the file can be computed by taking the size of the
block and dividing with the size of the charInfo structure,
i.e.: numChars = charsBlock.blockSize/20.


Block type 5: kerning pairs
--------------------------------------------------------------------------------
Field        Size  Type  Offset    Comment
--------------------------------------------------------------------------------
first           4  uint  (c*10)+0  These fields are repeated until
                                   all kerning pairs have been described
second          4  uint  (c*10)+4
amount          2  int   (c* 6)+8

This block is only in the file if there are any kerning pairs with amount
differing from 0.
*/

/*
Efnet/wiidev
<BlueChip> Is this the right place to find someone who understands the GX calls?
           If so, I have a texture which I want to display - but I want to draw
           it such that every which is opaque is reduced to black and opacity is
           multiplied by (0 < N <= 1) ...I think it should be possible via GX,
           but I really don't know how
<BlueChip> *every pixel
<+comex>   BlueChip_: what?
<BlueChip> i have a texture with a colourful square on it with a hole in the
           middle ...I wish to display it but every colour must be black and
           opacity must be changed by a factor
<rewt``>   ala wallhack?
<BlueChip> is that at me?
<rewt``>   yes
<BlueChip> googling "wallhack"...
<+comex>   http://www.amnoid.de/gc/tev.html
<BlueChip> I'm actually generating a 2d drop-shadow, but I guess it is no
           dissimilar to a wallhack
<rewt``>   it's a term in gaming where they use a hack to turn walls translucent
           to see enemies behind the walls
<+comex>   it's probably a trivial application of that
<BlueChip> comex: can you suggest which part of that page would be the most
           helpful bit for me?
<+comex>   from libogc's GX_SetTevOp

<+comex>   GX_SetTevColorOp(tevstage,GX_TEV_ADD,GX_TB_ZERO,GX_CS_SCALE_1,GX_TRUE,GX_TEVPREV);
<rewt``>   BlueChip_, look for "alpha"
<BlueChip> thanks, I will read and try to understand :)
<rewt``>   that's the opacity/translucency setting
<+comex>   or just
<+comex>   case GX_REPLACE:
<+comex>   GX_SetTevColorIn(tevstage,GX_CC_ZERO,GX_CC_ZERO,GX_CC_ZERO,GX_CC_TEXC);
<+comex>   change the last one to GX_CC_ZERO
<BlueChip> Thanks guys.  I'm gonna get a cuppa and set to work /now/ :)
<+comex>   and modify the alphain after that so that you get the multiplication you want

<BlueChip> I'm very new to the likes of GX (DX/GL/etc) ...so I could be a while,
           whilst I make sure I not only do this, but understand it too :) ...
           speak soon
*/

//------------------------------------------------------------------------------
static  BMF_State  BMFs;

//------------------------------------------------------------------------------
void  BMF_Init (void)
{
    int  i;

    // This is the ONLY alignment system currently implemented
    // Well, I say "implemented", it's more like "what happens if you don't
    // implement an alignment system" !
    BMFs.align  = BMF_ASCENT | BMF_LEFT;  // Veritcal and Horizontal Alignment

    BMFs.x      = 0;     // Screen coordinates (pixels; 0,0 is top-left)
    BMFs.y      = 0;     // ...

    BMFs.autolf = false; // Automatic \n at edge of screen
    BMFs.halfc  = true;  // Allow half-characters (characters partially off the screen)

    BMFs.l      = 0;                 // left   edge of display window
    BMFs.r      = rmode->fbWidth;    // right  edge of display window
    BMFs.t      = 0;                 // top    edge of display window
    BMFs.b      = rmode->xfbHeight;  // bottom edge of display window

    for (i = 0;  i < HOOK_CNT;  i++) {
        BMFs.hook[i].seq = HS_DEFAULT;  // Default loaded to a font
        BMFs.hook[i].ptf = NULL;        // No system-level hook
    }

    // Configure the default drawer
    BMFs.hook[HOOK_DRAWC].ptf = BMF_DRAWC_DFLT;
    memset(BMFs.args, 0, BMF_DRAWC_ARG_MAX);

}

//------------------------------------------------------------------------------
bool        BMF_hookSet (BMF_Font* font, int hookid, int seq, void*  ptf)
{
    BMF_Hook*  hp;

    if (hookid > HOOK_CNT)  return false ;

    hp = (font) ? (font->hook +hookid) : ( BMFs.hook +hookid) ;
    if (seq)  hp->seq = seq ;
    hp->ptf = ptf;

    return true ;
}

//------------------------------------------------------------------------------
BMF_Hook*   BMF_hookGet (BMF_Font* font, int hookid, BMF_Hook* hook)
{
    BMF_Hook*  hp;

    if (hookid > HOOK_CNT)  return NULL ;

    hp = (font) ? (font->hook +hookid) : (BMFs.hook +hookid) ;
    if (hook)  memcpy(hook, hp, sizeof(BMF_Hook)) ;

    return hp ;
}

//------------------------------------------------------------------------------
BMF_State*  BMF_stateGet (BMF_State* state)
{
    // if we were supplied a pointer to a state struct
    // fill it with a snapshot of the current state
    if (state)  memcpy(state, &BMFs, sizeof(BMF_State)) ;

    // Either way, we return a pointer to the live state-variables
    return &BMFs;
}

//------------------------------------------------------------------------------
void        BMF_statePut (BMF_State* state)
{
    if (state)  memcpy(&BMFs, state, sizeof(BMF_State)) ;
}

//------------------------------------------------------------------------------
// Create the internal BMF font from the image and header data
BMF_Font*  BMF_load (const void* png, const void* hdr)
{
    BMF_Font*      f;
    int            c, chrh, i, w, h;

    u8*            bp = (u8*)hdr;

    // Check header
    if ((*bp++ != 'B') || (*bp++ != 'M') || (*bp++ != 'F') || (*bp++ != 3))
        return (BMF_Font*)0 ;

    // Block 1 - Font & Image info
    if (*bp++ != 1)  return (BMF_Font*)-1 ;
    c = PEEK32_LE(bp);  bp += 4;
    chrh = PEEK16_LE(bp);
    bp += c;

    // Block 2 - Common character data
    if (*bp++ != 2)  return (BMF_Font*)-2 ;
    c = PEEK32_LE(bp);  bp += 4;
    w = PEEK16_LE(bp +4);
    h = PEEK16_LE(bp +6);
    bp += c;

    // Skip block 3 - page info
    if (*bp++ != 3)  return (BMF_Font*)-3 ;
    bp += 4 +PEEK32_LE(bp);

    // Block 4 - character map data
    if (*bp++ != 4)  return (BMF_Font*)-4 ;

    // How many characters are there in this set?
    c = PEEK32_LE(bp) / BMF_CHRSIZE;  bp += 4;
    if (!c)  return (BMF_Font*)-10 ;

    // Allocate enough memeory to store the font info
    i = sizeof(BMF_Font) +(c *sizeof(BMF_Char));
    if (!( f = malloc(i) ))  return (BMF_Font*)-9 ;
    f->msize  = i;

    // Font header
    f->tex    = GRRLIB_LoadTexture(png);  // Load font texture
    f->w      = w;
    f->h      = h;
    f->pixw   = 1.0 / w;
    f->pixh   = 1.0 / h;

    f->chr    = (BMF_Char*)(f+1);
    f->cnt    = c;

    f->yadv   = chrh;
    f->track  = 0;
    f->lead   = 0;

    for (i = 0;  i < HOOK_CNT;  i++) {
        f->hook[i].seq = BMFs.hook[i].seq;
        f->hook[i].ptf = NULL;
    }

    // Ensure all drawer args are set to 0 (for sanity)
    memset(f->args, 0, BMF_DRAWC_ARG_MAX);

    for (c = 0;  c < f->cnt;  c++)  {
        BMF_Char*  chr = f->chr +c;
        chr->id   = PEEK32_LE(bp);  bp += 4;
        chr->x    = PEEK16_LE(bp);  bp += 2;
        chr->y    = PEEK16_LE(bp);  bp += 2;
        chr->w    = PEEK16_LE(bp);  bp += 2;
        chr->h    = PEEK16_LE(bp);  bp += 2;
        chr->xoff = PEEK16_LE(bp);  bp += 2;
        chr->yoff = PEEK16_LE(bp);  bp += 2;
        chr->xadv = PEEK16_LE(bp);  bp += 2;
        /*chr->page = PEEK8_LE (bp);*/  bp += 1;
        /*chr->chnl = PEEK8_LE (bp);*/  bp += 1;

        chr->idx[BMF_TL].x = (chr->x         ) /(f32)f->tex->w;
        chr->idx[BMF_TL].y = (chr->y         ) /(f32)f->tex->h;
        //chr->idx[BMF_TL].z = 0;

        chr->idx[BMF_TR].x = (chr->x + chr->w) /(f32)f->tex->w;
        chr->idx[BMF_TR].y = chr->idx[BMF_TL].y;
        //chr->idx[BMF_TR].z = 0;

        chr->idx[BMF_BR].x = chr->idx[BMF_TR].x;
        chr->idx[BMF_BR].y = (chr->y + chr->h) /(f32)f->tex->h;
        //chr->idx[BMF_BR].z = 0;

        chr->idx[BMF_BL].x = chr->idx[BMF_TL].x;
        chr->idx[BMF_BL].y = chr->idx[BMF_BR].y;
        //chr->idx[BMF_BL].z = 0;
    }

    // Ignore block 5 - Kerning pairs
    //if (*bp++ != 5)  return (BMF_Font*)-5 ;
    //bp += 4 +PEEK32_LE(bp);

    return f;
}


//------------------------------------------------------------------------------
BMF_Char*  BMF_findc (const BMF_Font* font_in, const char c_in)
{
    BMF_Char  *chr;
    int       l, r, n;

    // We want the input prams as "const"
    //   but we may want to change them in the hooks!  So...
    BMF_Font*  font = (BMF_Font*)font_in;
    char       c    = (char)c_in;

//-in
    #define go(ptf) ((hook_findc_in)ptf)(&font, &c)
    #define HOOKID   (HOOK_FINDC_IN)
    #define ptfS    ( BMFs.hook[HOOKID].ptf)
    #define ptfF    (font->hook[HOOKID].ptf)
    switch          (font->hook[HOOKID].seq) {
        case HS_SYS_FNT:  if ( ptfS && !go(ptfS) ) break; //else fall-through
        case HS_FNT:      if ( ptfF && !go(ptfF) ) return NULL; else break;
        case HS_FNT_SYS:  if ( ptfF && !go(ptfF) ) break ; //else fall-through
        case HS_SYS:      if ( ptfS && !go(ptfS) ) return NULL; else break;
        default:          break;
    }
    #undef ptfF
    #undef ptfS
    #undef HOOKID
    #undef go

    // Binary chop to find the character we want
    l   = 0;             // left
    r   = font->cnt -1;  // right
    chr = font->chr;     // in case there's only 1 character
    while(l != r) {
        n   = l + ((r - l) / 2);          // mid point
        chr = font->chr +n;
        if      (chr->id > c)  r = n -1;  // lower half
        else if (chr->id < c)  l = n +1;  // upper half
        else                   break ;    // found it
    }
    if (l==r)  chr = font->chr +l ;   // was it the worst-case scenario
    if (chr->id != c)  return NULL ;  // We didn't find the char

//-out
    #define go(ptf) ((hook_findc_out)ptf)(&font, &c, &chr)
    #define HOOKID   (HOOK_FINDC_OUT)
    #define ptfS    ( BMFs.hook[HOOKID].ptf)
    #define ptfF    (font->hook[HOOKID].ptf)
    switch          (font->hook[HOOKID].seq) {
        case HS_SYS_FNT:  if ( ptfS && !go(ptfS) ) break; //else fall-through
        case HS_FNT:      if ( ptfF && !go(ptfF) ) break; else break;
        case HS_FNT_SYS:  if ( ptfF && !go(ptfF) ) break ; //else fall-through
        case HS_SYS:      if ( ptfS && !go(ptfS) ) break; else break;
        default:          break;
    }
    #undef ptfF
    #undef ptfS
    #undef HOOKID
    #undef go

    return chr;
}

//------------------------------------------------------------------------------
int  BMF_cr(const BMF_Font* font)
{
    BMFs.x  = BMFs.l;
    BMFs.y += font->yadv + font->lead;
    return 0;
}

//------------------------------------------------------------------------------
int  BMF_lf(const BMF_Font* font)
{
    BMFs.y += (font->yadv + font->lead) /2;
    return 0;
}

//------------------------------------------------------------------------------
int  BMF_putc (const BMF_Font* font_in, const char c_in)
{
    BMF_Char  *chr;
    f32       x, y;

//-in
    // We want the input prams as "const"
    //   but we may want to change them in the hooks!  So...
    BMF_Font*  font = (BMF_Font*)font_in;
    char       c    = (char)c_in;

    #define go(ptf) ((hook_putc_in)ptf)(&font, &c)
    #define HOOKID   (HOOK_PUTC_IN)
    #define ptfS    ( BMFs.hook[HOOKID].ptf)
    #define ptfF    (font->hook[HOOKID].ptf)
    switch          (font->hook[HOOKID].seq) {
        case HS_SYS_FNT:  if ( ptfS && !go(ptfS) ) break; //else fall-through
        case HS_FNT:      if ( ptfF && !go(ptfF) ) return 0; else break;
        case HS_FNT_SYS:  if ( ptfF && !go(ptfF) ) break ; //else fall-through
        case HS_SYS:      if ( ptfS && !go(ptfS) ) return 0; else break;
        default:          break;
    }
    #undef ptfF
    #undef ptfS
    #undef HOOKID
    #undef go

    // Requested Newline
    if (c == '\n')  return BMF_cr(font) ;  // returns 0

    // Requested half-Newline
    if (c == '\r')  return BMF_lf(font) ;  // returns 0

///! I'd like to see tab stop definitions rather than an arbitrary shift amount
    // Requested tab
    if (c == '\t')  {
        int  tab;
        if ( !(chr = BMF_findc(font, 'm')) )  return 0 ;
        tab = 3 *chr->xadv;
        BMFs.x += tab;
        return tab;
    }

    // Try to find the chr we want, bail now if we fail
    if ( !(chr = BMF_findc(font, c)) )  return 0 ;

    // --------
    // Should we even try to print the character
    // -1 => give up;  -2 => a future \n may fix the problem
    // --------
    // NOTE: The order of these checks is critical!!
    if (!BMFs.halfc) {  // ? half-characters are BANNED
        // ? off the top
        if (BMFs.y < BMFs.t)
            /*if (BMFs.Autolf)     // get on the screen
                do  BMF_cr(font);  while(BMFs.BMFy < 0) ;
            else  */return -2 ;    // wait for \n

        // ? off the left
        if        (BMFs.x < BMFs.l) {           // string may reach screen
            BMFs.x += chr->xadv +font->track;
            return -2;
        // ? off the right
        } else if (BMFs.x +chr->xadv > BMFs.r) {
            if (BMFs.autolf)  BMF_cr(font) ;    // auto-lf
            else              return -2 ;       // wait for \n
        }

        // ? off the bottom
        if (BMFs.y +font->yadv > BMFs.b)  return -1 ; // give up now!

    } else {  // half-characters allowed
        // ? off the top
        if (BMFs.y < (BMFs.t -font->yadv))
            /*if (BMFs.Autolf)     // get on the screen
                do  BMF_cr(font);  while(BMFs.y < (-font->yadv)) ;
            else  */return -2 ;    // wait for \n

        // ? off the left
        if        (BMFs.x < (BMFs.l -chr->xadv)) {  // string may reach screen
            BMFs.x += chr->xadv +font->track;
            return -2;
        // ? off the right
        } else if (BMFs.x > BMFs.r) {
            if (BMFs.autolf)  BMF_cr(font) ;        // auto-lf
            else              return -2 ;           // wait for \n
        }

        // ? off the bottom
        if (BMFs.y > BMFs.b)  return -1 ;  // give up now!
    }

//-lf
    // We may wish to tweak the x & y without changing the BMF_State table
    x = BMFs.x;
    y = BMFs.y;

    #define go(ptf) ((hook_putc_pre)ptf)(&font, &c, &x, &y, &chr)
    #define HOOKID   (HOOK_PUTC_PRE)
    #define ptfS    ( BMFs.hook[HOOKID].ptf)
    #define ptfF    (font->hook[HOOKID].ptf)
    switch          (font->hook[HOOKID].seq) {
        case HS_SYS_FNT:  if ( ptfS && !go(ptfS) ) break; //else fall-through
        case HS_FNT:      if ( ptfF && !go(ptfF) ) return -2; else break;
        case HS_FNT_SYS:  if ( ptfF && !go(ptfF) ) break ; //else fall-through
        case HS_SYS:      if ( ptfS && !go(ptfS) ) return -2; else break;
        default:          break;
    }
    #undef ptfF
    #undef ptfS
    #undef HOOKID
    #undef go

    // If we got this far, print the character, already! :)
    // Try to use first the font drawer then the system drawer
    if      (font->hook[HOOK_DRAWC].ptf)
        ((hook_drawc)font->hook[HOOK_DRAWC].ptf)(font->args, x, y, font, chr) ;
    else if (BMFs. hook[HOOK_DRAWC].ptf)
        ((hook_drawc)BMFs. hook[HOOK_DRAWC].ptf)(BMFs .args, x, y, font, chr) ;
    else    return -1;  // No drawers defined - give up!

    BMFs.x += chr->xadv +font->track;  // Advance the cursor

//-disp
    #define go(ptf) ((hook_putc_post)ptf)(&font, &c, &x, &y, &chr)
    #define HOOKID   (HOOK_PUTC_POST)
    #define ptfS    ( BMFs.hook[HOOKID].ptf)
    #define ptfF    (font->hook[HOOKID].ptf)
    switch          (font->hook[HOOKID].seq) {
        case HS_SYS_FNT:  if ( ptfS && !go(ptfS) ) break; //else fall-through
        case HS_FNT:      if ( ptfF && !go(ptfF) ) goto end; else break;
        case HS_FNT_SYS:  if ( ptfF && !go(ptfF) ) break ; //else fall-through
        case HS_SYS:      if ( ptfS && !go(ptfS) ) goto end; else break;
        default:          break;
    }
    #undef ptfF
    #undef ptfS
    #undef HOOKID
    #undef go

    // If we want auto-lf, then check if we are off the side of the screen now
    if (BMFs.autolf && (BMFs.x > BMFs.r))  BMF_cr(font) ;

//-out
    #define go(ptf) ((hook_putc_out)ptf)(&font, &c, &chr)
    #define HOOKID   (HOOK_PUTC_OUT)
    #define ptfS    ( BMFs.hook[HOOKID].ptf)
    #define ptfF    (font->hook[HOOKID].ptf)
    switch          (font->hook[HOOKID].seq) {
        case HS_SYS_FNT:  if ( ptfS && !go(ptfS) ) break; //else fall-through
        case HS_FNT:      if ( ptfF && !go(ptfF) ) break; else break;
        case HS_FNT_SYS:  if ( ptfF && !go(ptfF) ) break ; //else fall-through
        case HS_SYS:      if ( ptfS && !go(ptfS) ) break; else break;
        default:          break;
    }
    #undef ptfF
    #undef ptfS
    #undef HOOKID
    #undef go

end:
    return chr->xadv +font->track;
}


//------------------------------------------------------------------------------
int  BMF_puts (const BMF_Font* font_in, const char* s_in)
{
    char  *cp;
    int   w;
    int   len = 0;

//-in
    // We want the input prams as "const"
    //   but we may want to change them in the hooks!  So...
    BMF_Font*  font = (BMF_Font*)font_in;
    char*      s    = (char*)s_in;

    #define go(ptf) ((hook_puts_in)ptf)(&font, &s)
    #define HOOKID   (HOOK_PUTS_IN)
    #define ptfS    ( BMFs.hook[HOOKID].ptf)
    #define ptfF    (font->hook[HOOKID].ptf)
    switch          (font->hook[HOOKID].seq) {
        case HS_SYS_FNT:  if ( ptfS && !go(ptfS) ) break; //else fall-through
        case HS_FNT:      if ( ptfF && !go(ptfF) ) return 0; else break;
        case HS_FNT_SYS:  if ( ptfF && !go(ptfF) ) break ; //else fall-through
        case HS_SYS:      if ( ptfS && !go(ptfS) ) return 0; else break;
        default:          break;
    }
    #undef ptfF
    #undef ptfS
    #undef HOOKID
    #undef go

    for (cp = (char*)s;  *cp;  cp++)
        switch (( w = BMF_putc(font, *cp) )) {
            default:  len += w;  // Track pixel length of string
            case -2:  break;     // putc() said "no char printed"
            case -1:  goto eol;  // putc() said "stop string now"
        }
    eol:

//-out
    #define go(ptf) ((hook_puts_out)ptf)(&font, &s, &len)
    #define HOOKID   (HOOK_PUTS_OUT)
    #define ptfS    ( BMFs.hook[HOOKID].ptf)
    #define ptfF    (font->hook[HOOKID].ptf)
    switch          (font->hook[HOOKID].seq) {
        case HS_SYS_FNT:  if ( ptfS && !go(ptfS) ) break; //else fall-through
        case HS_FNT:      if ( ptfF && !go(ptfF) ) break; else break;
        case HS_FNT_SYS:  if ( ptfF && !go(ptfF) ) break ; //else fall-through
        case HS_SYS:      if ( ptfS && !go(ptfS) ) break; else break;
        default:          break;
    }
    #undef ptfF
    #undef ptfS
    #undef HOOKID
    #undef go

    return len;
}

//------------------------------------------------------------------------------
void  BMF_halfc (const bool b)
{
    BMFs.halfc = b;
}

//------------------------------------------------------------------------------
void  BMF_autolf (const bool b)
{
    BMFs.autolf = b;
}

//------------------------------------------------------------------------------
void  BMF_align (const int a)
{
    BMFs.align = a;
}

//------------------------------------------------------------------------------
void  BMF_halign (const int a)
{
    BMFs.align = (BMFs.align & BMF_MASK_VERT) | a;
}

//------------------------------------------------------------------------------
void  BMF_valign (const int a)
{
    BMFs.align = (BMFs.align & BMF_MASK_HORZ) | a;
}

//------------------------------------------------------------------------------
void  BMF_goto (const int x, const int y)
{
    BMFs.x = (f32)x;
    BMFs.y = (f32)y;
}

//------------------------------------------------------------------------------
void  BMF_home (void)
{
    BMF_goto(BMFs.l, BMFs.t);  // goto top-left (x,y)
}

//------------------------------------------------------------------------------
void  BMF_area (const int left, const int top, const int right, const int btm)
{
    BMFs.l = left;
    BMFs.t = top;
    BMFs.r = right;
    BMFs.b = btm;

    BMFs.x = (f32)left;
    BMFs.y = (f32)top;
}

//------------------------------------------------------------------------------
void  BMF_lead (BMF_Font* font, const int l)
{
    font->lead = l;
}

//------------------------------------------------------------------------------
void  BMF_track (BMF_Font* font, const int t)
{
    font->track = t;
}

//------------------------------------------------------------------------------
void  BMF_getcur (int* x, int* y)
{
    *x = (int)BMFs.x;
    *y = (int)BMFs.y;
}

//------------------------------------------------------------------------------
int   BMF_slen (const BMF_Font* font, const char* s)
{
    int       len;
    char      *cp;
    BMF_Char  *chr;

    for(cp = (char*)s, len = 0;  *cp;  cp++)
        if ( (chr = BMF_findc(font, *cp)) )  len += chr->xadv +font->track ;
    return len;
}
